require 'rubygems'
require 'optparse'
require 'cucumber'

require "#{File.dirname(__FILE__)}/mingle"
require "#{File.dirname(__FILE__)}/erb_formatter"

module Cucumber::Mingle
  class CLI
    class << self
      attr_writer :step_mother, :executor, :features
    
      def execute
        @execute_called = true
        parse(ARGV).execute!(@step_mother, @executor)
      end
      
      def execute_called?
        @execute_called
      end

      def parse(args)
        cli = new
        cli.parse_options!(args)
        cli
      end
    end    
    attr_reader :options
    FORMATS = %w{basic}
    
    def parse_options!(args)
      return parse_args_from_profile('default') if args.empty?
      args.extend(OptionParser::Arguable)

      @options ||= { 
        :require => ['steps'], 
        :lang    => 'en', 
        :task    => false,
        :format  => 'basic',
        :filters => ['[Type][is][Story]', '[Status][is greater than][Dev In Progress]'], 
        :source  => true
      }
      args.options do |opts|
        opts.banner = "Usage: cucumber-mingle [options]"
        opts.on("-r LIBRARY|DIR", "--require LIBRARY|DIR", "Require files before executing the features.",
          "If this option is not specified, all *.rb files that",
          "are siblings or below the features will be autorequired") do |v|
          @options[:require] ||= []
          @options[:require] << v
        end
        opts.on("-a LANG", "--language LANG", "Specify language for features (Default: #{@options[:lang]})",
          "Available languages: #{Cucumber.languages.join(", ")}",
          "Look at #{Cucumber::LANGUAGE_FILE} for keywords") do |v|
          @options[:lang] = v
        end
        opts.on("-f FORMAT", "--format FORMAT", "How to format features (Default: #{@options[:format]})",
          "Available formats: #{FORMATS.join(", ")}") do |v|
          unless FORMATS.index(v) 
            STDERR.puts "Invalid format: #{v}\n"
            STDERR.puts opts.help
            exit 1
          end
          @options[:format] = v
        end
        opts.on("-p=PROFILE", "--profile=PROFILE", "Pull commandline arguments from cucumber.yml.") do |v|
          parse_args_from_profile(v)
        end
        opts.on("-n", "--no-source", "Don't show the file and line of the step definition with the steps.") do
          @options[:source] = false
        end
        opts.on("-s=URL", "--site=URL", "Specify Mingle url.") do |v|
          @options[:site] = v          
        end
        opts.on('', "--filters FILTERS", "Specify Mingle filters to get cards (Default: #{options[:filters].join(' ')})") do |v|
          @options[:filters] = v
        end
        opts.on('-t', "--task", "Create task for manual testing.") do
          @options[:task] = true
        end
        opts.on_tail("--help", "You're looking at it") do
          puts opts.help
          exit
        end
      end.parse!      
    end

    def execute!(step_mother, executor)
      setup_mingle
      setup_parser
      executor.formatter = Formatters::ErbFormmatter.new(@options[:format], step_mother, @options)
      require_files
      
      features = get_features_from_mingle
      if features.length == 0
        print "0 test scenario found\r\n"
        exit 0 
      end  
      executor.visit_features(features)
      exit 1 if executor.failed
    end

    private    
    def parse_args_from_profile(profile)
      require 'yaml'
      cucumber_yml = YAML::load(IO.read('cucumber.yml'))
      args_from_yml = cucumber_yml[profile]
      raise "Expected to find a String, got #{args_from_yml.inspect}. cucumber.yml:\n#{cucumber_yml}" unless String === args_from_yml
      parse_options!(args_from_yml.split(' '))
    end
        
    def setup_mingle
      raise "Mingle not specified" unless @options[:site]
      Card.site = @options[:site]      
    end
    
    def setup_parser
      Cucumber.load_language(@options[:lang])      
      require "cucumber/treetop_parser/feature_#{@options[:lang]}"
      require "cucumber/treetop_parser/feature_parser"
      @parser = Cucumber::TreetopParser::FeatureParser.new
    end
    
    def require_files
      return unless @options[:require]
      libs = @options[:require].map do |path|
        path = path.gsub(/\\/, '/') # In case we're on windows. Globs don't work with backslashes.
        File.directory?(path) ? Dir["#{path}/**/*.rb"] : path
      end.flatten.uniq
      libs.each do |lib|
        begin
          require lib if File.exist?(lib)
        rescue LoadError => e
          e.message << "\nFailed to load #{lib}"
          raise e
        end
      end
    end
    
    def get_features_from_mingle
      cards = Card.find(:all, :params => { :filters => @options[:filters] })
      
      features = Cucumber::Tree::Features.new      
      cards.each do |card|
        feature = parse_feature(card.description)
        class << feature; attr_accessor :card; end
        feature.card = card
        features << feature
      end
      features      
    end
    
    def parse_feature(feature)
      ast = @parser.parse(feature)
      if ast.nil?
        raise SyntaxError.new(file, self)
      else
        feature = ast.compile
        feature
      end
    end        
  end
end

extend Cucumber::StepMethods
Cucumber::Mingle::CLI.step_mother = step_mother
Cucumber::Mingle::CLI.executor = executor
extend Cucumber::Tree
